#pragma once
#include <list>
#include <vector>
#include <string>
#include "utils/Errors.h"
#include "utils/Pointers.h"

struct Tag;

struct TagLocation{
	TagLocation()
	: fileName("")
	, pattern("")
	, line(0)
	{
	}
	const char* fileName;
	const char* pattern;
	int line;
};

enum TagType{
	TAG_CLASS,
	TAG_ENUM,
	TAG_FUNCTION,
	TAG_PROTOTYPE,
	TAG_TYPEDEF,
	TAG_MEMBER,
	TAG_ENUM_VALUE,
	TAG_DEFINE,
	TAG_UNKNOWN,
	TAG_TYPE_COUNT
};

class TagRef{
public:
	TagRef(int index = -1, TagType type = TAG_UNKNOWN)
	: index_(index)
	, type_(type)
	{
	}
	operator bool() const{
		return index_ != -1;
	}
	//inline Tag& operator*() const;
	//inline Tag* operator->() const;
protected:
	int index_;
	TagType type_;
};

struct Tag{
	Tag()
	: name("")
	, nameFull("")
	, title("")
	, titleFull("")
	, signature(0)
	, parentClass(0)
	, type(TAG_UNKNOWN)
	{
	}
	TagType type;
	
	const char* name;
	const char* nameFull;
	const char* signature;

	const char* title;
	const char* titleFull;
	TagLocation location;
	const char* parentClass;
	//TagRef parent;
	//typedef std::vector<TagRef> Members;
	//Members members;
};

class TagStorage;

class TagNode : public RefCounter{
public:
	TagNode(TagStorage* storage);
	virtual ~TagNode() {}

	TagNode* parent(){ return parent_; }
	TagStorage* storage(){ return storage_; }

	bool loadFromFile(const char* tagsFileName);
	bool parseSource(const char* sourceFileName);
	void clear();

	typedef std::vector<Tag> Tags;
	virtual bool rescan() { return false; }

	const Tags& tags(TagType type) const{ return tags_[int(type)]; }

	void add(TagNode* newChild);
	void remove(TagNode* node);

	void setParent(TagNode* newParent);
	void setName(const char* name){ name_ = name; }
	const char* name() const{ return name_.c_str(); }

	TagNode* findByName(const char* name);
protected:
	typedef std::list<std::string> Strings;
	Strings strings_;
	const char* addConstString(const char* str);

	TagNode* parent_;
	TagStorage* storage_;
	typedef std::vector< SharedPtr<TagNode> > Children;
	Children children_;
	std::string name_;
	Tags tags_[TAG_TYPE_COUNT];
};

class TagStorage;
class TagSource : public RefCounter{
public:
	TagSource(TagStorage* storage)
	: storage_(storage)
	, root_(storage) {}

	virtual ~TagSource() {}
	const char* path() const{ return path_.c_str(); }

	virtual bool poll() = 0;

	TagNode* root(){ return &root_; }
	TagStorage* storage() { return storage_; }
protected:
	TagStorage* storage_;
	std::string path_;
	TagNode root_;
};

class TagStorage{
public:
	TagStorage();

	void clear();
	void addSource(TagSource* newSource);

	void addTagsFromNode(TagNode* node);
	void removeTagsByNode(TagNode* node);
	/*
	TagRef findTagByName(const char* name, TagType type = TAG_UNKNOWN);
	Tag& tagByIndex(int index, TagType type){
		ASSERT(index >= 0 && index < int(tags_[int(type)].size()));
		return tags_[int(type)][index];
	}
	*/
	typedef std::vector<const Tag*> SortedTags;
	const SortedTags& sortedTags(TagType type) const{ return sortedTags_[int(type)]; }

	bool pollSources();

	typedef std::vector<const Tag*> DeadTags;
	const DeadTags& deadTags(){ return deadTags_; }

	typedef std::vector<const Tag*> NewTags;
	const NewTags& newTags(){ return newTags_; }

	void flushTags(){
		deadTags_.clear();
		newTags_.clear();
	}
protected:
	void sortedInsert(SortedTags& sortedTags, const Tag* tagToInsert);
	SortedTags sortedTags_[TAG_TYPE_COUNT];

	typedef std::vector<SharedPtr< TagSource > > Sources;
	Sources sources_;
	DeadTags deadTags_;
	NewTags newTags_;

};

extern TagStorage tagStorage;



inline bool compareSymbolName(const char* lhs, const char* rhs)
{
	while(*lhs == '~')
		++lhs;
	while(*rhs == '~')
		++rhs;
	return strcmp(lhs, rhs) < 0;
}

struct TagCompare{
	bool operator()(const Tag* lhs, const Tag* rhs){
		if(lhs->type == rhs->type)
			return compareSymbolName(lhs->name, rhs->name);
		else
			return lhs->type < rhs->type;
	}
};

void updateTagsFile(const char* tagsFile, const char* sourceFile);
/*
inline Tag& TagRef::operator*() const{
	return tagStorage.tagByIndex(index_, type_);
}
inline Tag* TagRef::operator->() const{
	return &tagStorage.tagByIndex(index_, type_);
}


*/